package com.tplhk.thread.threadlocal;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.TtlExecutorTransformlet;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.sql.SQLOutput;
import java.util.concurrent.*;

/**
 * @ClassName : TestThreadLocal
 * @Description : TODO
 * @Author : taiping
 * @Date: 2021/6/12 17:31
 **/
@Slf4j
public class TestTransmittableThreadLocal {

    private static ThreadLocal<String> tl = new TransmittableThreadLocal();
    private static ThreadLocal<Hello> t2 = new TransmittableThreadLocal();

    /**
     * 这种方式应用在 testThreadLocal2,
     * 传入的 runable ,callable 需要用 TtlRunnable, TtlCallable 修饰
     */
    private ExecutorService executorService = new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

    /**
     * 这种方式应用在 testThreadLocal3, 需要将原生 executorService 包装
     * 直接传入的 runable ,callable
     * <p>
     * 通过工具类com.alibaba.ttl.threadpool.TtlExecutors完成，有下面的方法：
     * getTtlExecutor：修饰接口Executor
     * getTtlExecutorService：修饰接口ExecutorService
     * getTtlScheduledExecutorService：修饰接口ScheduledExecutorService
     */
    private ExecutorService ttlExecutors = TtlExecutors.getTtlExecutorService(executorService);


    /**
     * 结论：
     * 父类的基础变量、对象都可以传到子类； 子类修改基本变量只能作用在子类，但修改对象可以作用全局(包括兄弟线程）
     * <p>
     * 下例打印结果：
     * 当前线程名称: main, main方法内获取线程内数据为: value-set-in-parent, init
     * 当前线程名称: Thread-1, main方法内获取线程内数据为: value-set-in-parent, init
     * 当前线程名称: Thread-1, main方法内获取线程内数据为: value-set-in-son, init2
     * 当前线程名称: main, main方法内获取线程内数据为: value-set-in-parent, init2
     *
     * @throws Exception
     */
    public void testThreadLocal() throws Exception {
        tl.set("value-set-in-parent");
        Hello hello = new Hello();
        hello.setName("对象值 init");
        t2.set(hello);

        fc();
        new Thread(() -> {
            fc();
            tl.set("value-set-in-son");
            t2.get().setName("对象值 init2");
            fc();
        }).start();
        Thread.sleep(1000L);
        fc();
    }

    /**
     * 线程池中变量的传递: 结论同 testThreadLocal()
     * <p>
     * <p>
     * 下例打印结果：
     * 当前线程名称: main, 分别读取基础数据类型 value-set-in-parent, 对象值对象值 init
     * 当前线程名称: pool-1-thread-1, 分别读取基础数据类型 value-set-in-parent, 对象值对象值 init
     * 当前线程名称: pool-1-thread-1, 分别读取基础数据类型 values-set-in-parent 修改一下, 对象值对象值 init 修改一下
     * ---------main 线程读取值
     * 当前线程名称: main, 分别读取基础数据类型 value-set-in-parent, 对象值对象值 init 修改一下
     * ---------子线程中修改值的结论 ：修改基础数据类型只影响子线程；修改对象值影响全局
     * ---------main 线程修改值，观察对子线程的影响
     * 当前线程名称: pool-1-thread-2, 分别读取基础数据类型 values-set-in-parent 修改一下*2, 对象值对象值 init 修改一下*2
     */
    public void testThreadLocal2() throws InterruptedException {
        tl.set("value-set-in-parent");
        Hello hello = new Hello();
        hello.setName("对象值 init");
        t2.set(hello);
        fc();

        /////////////////////////////////////
        // Runnable / TtlRunnable 的写法
        /////////////////////////////////////
        Runnable task = () -> {
            fc();
            tl.set("values-set-in-parent 修改一下");
            t2.get().setName("对象值 init 修改一下");
            fc();
        };

        TtlRunnable ttlRunnable = TtlRunnable.get(task);
        executorService.execute(ttlRunnable);

        Thread.sleep(1000 * 2);
        log.info("---------main 线程读取值");
        fc();
        log.info("---------子线程中修改值的结论 ：修改基础数据类型只影响子线程；修改对象值影响全局");

        log.info("---------main 线程修改值，观察对子线程的影响");
        tl.set("values-set-in-parent 修改一下*2");
        t2.get().setName("对象值 init 修改一下*2");

        Thread.sleep(1000 * 2);
        // 注意::::::::::::::
        // 即使是同一个Runnable任务多次提交到线程池时，
        // 每次提交时都需要修饰操作（即TtlRunnable.get(task)），
        // 以抓取最新的TransmittableThreadLocal上下文的值
        TtlRunnable ttlRunnable1 = TtlRunnable.get(task);
        executorService.execute(ttlRunnable1);
        /////////////////////////////////////
        // Callable / TtlCallable 的写法
        /////////////////////////////////////
//        Callable call = () -> {
//            log.info("[child thread] get {} in Runnable", tl.get());
//            return null;
//        };
//
//        TtlCallable ttlCallable = TtlCallable.get(call);
//
//        executorService.submit(ttlCallable);

        /////////////////////////////////////
        // cleanup
        /////////////////////////////////////
        executorService.shutdown();
    }


    /**
     * 修改 testThreadLocal2，省去每次Runnable和Callable传入线程池时的修饰
     *
     * @throws InterruptedException
     */

    public void testThreadLocal3() throws InterruptedException {
        tl.set("value-set-in-parent");
        Hello hello = new Hello();
        hello.setName("对象值 init");
        t2.set(hello);
        fc();

        /////////////////////////////////////
        // Runnable / TtlRunnable 的写法
        /////////////////////////////////////
        Runnable task = () -> {
            fc();
            tl.set("values-set-in-parent 修改一下");
            t2.get().setName("对象值 init 修改一下");
            fc();
        };

        ttlExecutors.execute(task);

        Thread.sleep(1000 * 2);
        log.info("---------main 线程读取值");
        fc();
        log.info("---------子线程中修改值的结论 ：修改基础数据类型只影响子线程；修改对象值影响全局");

        log.info("---------main 线程修改值，观察对子线程的影响");
        tl.set("values-set-in-parent 修改一下*2");
        t2.get().setName("对象值 init 修改一下*2");

        Thread.sleep(1000 * 2);
        ttlExecutors.execute(task);


        ttlExecutors.shutdown();
    }


    private static void fc() {
        log.info("当前线程名称: {}, 分别读取基础数据类型 {}, 对象值{} ",
                Thread.currentThread().getName(), tl.get(), t2.get().getName());
    }


    public static void main(String[] args) throws Exception {
        TestTransmittableThreadLocal testThreadLocal = new TestTransmittableThreadLocal();
//        testThreadLocal.testThreadLocal();
//        testThreadLocal.testThreadLocal2();
        testThreadLocal.testThreadLocal3();
    }

}
